package edu.kit.ksri.db.mturk.simulations.performance;

import java.util.Calendar;

import org.apache.log4j.Logger;

import edu.kit.ksri.db.mturk.var.StaticInformation;

/**
 * Copyright (c) 2010-2011 Karlsruhe Institute of Technology (KIT), Germany
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
 * --------------------------------------------------------------------------
 * 
 * Software: CSP/WMV tool for dynamic quality management of microtasks
 * http://www.ksri.kit.edu/Upload/Users/PeopleClouds/HCOMP2011_An_extendable_toolkit_for_managing_quality_of_human-based_eServices.pdf
 * 
 * Organization: Karlsruhe Institute of Technology (KIT), Germany
 * http://www.kit.edu
 * 
 * Project: PeopleClouds project of the Karlsruhe Service Research Institute(KSRI)
 * http://www.ksri.kit.edu/Default.aspx?PageId=818
 * 
 * Contributing authors: David Bermbach, Sandra Rath, Pascal Wichmann, Robert Kern
 * 
 * ---------------------------------------------------------------------------
 * 
 * Purpose: Simulates a worker
 */
public class PerfSimWorker implements Runnable {

	private static final Logger basiclog = Logger.getLogger("basic." + PerfSimWorker.class);
	
	/** the worker id */
	private final String workerId;

	/**
	 * stores all possible answers of this worker, items may be null if the
	 * worker cannot provide an answer to that question id
	 */
	private final String[] possibleAnswers;

	/**
	 * the qualification value determines whether a worker may work on a given
	 * task or not. cf. also {@link StaticInformation}
	 */
	private int qualificationValue = 0;

	/** determines what the worker would do next */
	private PerfSimWorkerState state = PerfSimWorkerState.WaitingForNextHIT;

	/**
	 * references a position in possibleAnswers, is retrieved from
	 * {@link PerfSimMTurk}.
	 */
	private int currentHitId = -1;

	/**
	 * @param workerId
	 * @param possibleAnswers
	 *            all possible answers of this worker, items may be null if the
	 *            worker cannot provide an answer to that question id
	 * @param qualificationValue
	 *            the qualification values, determines whether a worker may work
	 *            on a given task or not. cf. also {@link StaticInformation}.
	 */
	public PerfSimWorker(String workerId, String[] possibleAnswers,
			int qualificationValue) {
		super();
		this.workerId = workerId;
		this.possibleAnswers = possibleAnswers;
		this.qualificationValue = qualificationValue;
		basiclog.info("Created worker " + workerId + " with qualification value "
				+ qualificationValue + " and " + possibleAnswers.length
				+ " answers.");
	}

	public void run() {
		basiclog.info("Worker " + workerId + " started.");
		while (PerfSimMTurk.isRunning()) {
			switch (state) {
			case WaitingForNewQualificationValue: {
				int newQuali = 0;
				try {
					newQuali = PerfSimMTurk.getQualificationUpdate(workerId);
					basiclog
							.info(workerId+": Got new qualification value "
									+ newQuali
									+ ", is now "
									+ (newQuali < StaticInformation.activePoolQualificationValue ? "blocked"
											: "allowed"));
				} catch (Exception e) {
					break;
				}
				this.qualificationValue = newQuali;
				if (newQuali < StaticInformation.activePoolQualificationValue) {
					// retry later...do nothing
				} else {
					state = PerfSimWorkerState.WaitingForNextHIT;
				}
				break;
			}
			case WaitingForNextHIT: {
				if (Math.random() < StaticInformation.hitAcceptanceProbabilityForSimWorkers) {
					// check whether there is a qualification update
					int newQuali = 0;
					try {
						newQuali = PerfSimMTurk.getQualificationUpdate(workerId);
						this.qualificationValue = newQuali;
						basiclog
						.info(workerId+": Got new qualification value "
								+ qualificationValue
								+ ", is now "
								+ (this.qualificationValue < StaticInformation.activePoolQualificationValue ? "blocked"
										: "allowed"));
					} catch (Exception e) {
						// no need to do anything at all
					}

					if (this.qualificationValue < StaticInformation.activePoolQualificationValue) {
						state = PerfSimWorkerState.WaitingForNewQualificationValue;
						break;
					}
					// qualification is ok, now poll for a hit
					int hit = -1;
					try {
						hit = PerfSimMTurk.getNextHIT();
					} catch (Exception e) {
						break;
					}
					if (possibleAnswers[hit] == null) {
						// return hit
						PerfSimMTurk.addHIT(hit);
					} else {
						state = PerfSimWorkerState.Working;
						currentHitId = hit;
					}
				}
				break;
			}
			case Working: {
				if (currentHitId != -1) {
					String answer = possibleAnswers[currentHitId];
					possibleAnswers[currentHitId] = null;
					if (answer == null) {
						// should never happen, was already checked in state
						// WaitingForNextHIT
						state = PerfSimWorkerState.WaitingForNextHIT;
						PerfSimMTurk.addHIT(currentHitId);
					} else {
						basiclog.info(workerId+": Working on hit " + currentHitId);
						// wait till we can return the answer
						long delay = StaticInformation.maxTaskDurationForSimWorkers
								- StaticInformation.minTaskDurationForSimWorkers;
						delay = (long) (Math.random() * (delay + 1));
						delay += StaticInformation.minTaskDurationForSimWorkers;
						Calendar acceptTime = Calendar.getInstance();
						try {
							Thread.sleep(delay);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						PerfSimMTurk.addAnswer(answer, currentHitId, workerId,
								acceptTime);
						currentHitId = -1;
					}
				} else
					state = PerfSimWorkerState.WaitingForNextHIT;
				break;
			}
			default: {
				throw new RuntimeException("Unsupported state: " + state);
			}
			}
			try {
				Thread.sleep(StaticInformation.updateIntervalForSimWorkers);
			} catch (InterruptedException e) {
				// does not matter because it should not happen
				e.printStackTrace();
			}

		}

	}

}
